import socket, json, time
from threadPool import ThreadPool
from Queue import Queue
from nbhttpconnection import NBHTTPConnection

class FollwitAPI():
    def __init__(self, username, password, debug=False):
        self.apikey = '0MAKf8epbYSUb'
        self.host = 'follw.it'
        self.acceptedIDs = ['movie_id', 'imdb_id', 'tmdb_id']
        if debug:
            self.debug = True
        else:
            self.debug = False
        #set username and password for api calls
        self.username = username
        self.password = password
        self.threadpool = ThreadPool(5)
        self.responseQueue = Queue()
        
    # method to close threads in the threadpool    
    def close(self):
        self.threadpool.joinAll(False,False)
        
    def clearQueue(self):
        self.responseQueue = None
        self.responseQueue = Queue()
    
    # Method that invokes an API call.
    # @param method     type of request (GET, POST)
    # @param call       API method to invoke
    # @param args       dictionary of arguments to send to api
    # @param push       decides if method waits for a return value
    def remoteAPI(self, method, call, data={}, queue=False):
        if queue:
            self.threadpool.queueTask(self.threadedJsonRequest, ('POST', '/api/3/${apikey}/'+call, self.username, self.password, data, self.host, self.apikey, self.debug), self.successfulResponce)
        else:
            return self.jsonRequest(method, '/api/3/${apikey}/'+call, data)
        
    
    # method that preforms http requests to the follwit server
    # @param method     type of request (GET, POST)
    # @param req        path of request after hostname /api/3/...
    # @param args       dictionary of arguments to send to api
    # @param push       decides if method waits for a return value
    def jsonRequest(self, method, req, args={}):
        try:
            conn = NBHTTPConnection(self.host)
        except socket.timeout:
            print '[Follwit API] socket timeout excecption'
            conn = None
        if conn == None:
            if self.debug:
                print 'Unable to connect to follwit'
            data = {}
            data['response'] = 'failure'
            data['message'] = 'Unable to connect to follwit'
            data['data'] = args
            return data
    
        try:
            req = req.replace('${apikey}',self.apikey)
            if method == 'POST':
                args['username'] = self.username
                args['password'] = self.password
                jdata = json.dumps(args)
                conn.request('POST', req, jdata)
            elif method == 'GET':
                conn.request('GET', req)
            else:
                return 'method error'
        except socket.error:
            if self.debug:
                print 'Unable to connect to follwit'
            data = {}
            data['response'] = 'failure'
            data['message'] = 'Unable to connect to follwit'
            data['password'] = '[Password Removed]'
            data['data'] = args
            conn.close()
            return data
        
        jdata = str(jdata).replace(self.password,'[Password Removed]')
        if self.debug:
            print 'Request string ' + req
            print 'Request args ' + jdata
        
        conn.go()
        
        timeout = 0
        while timeout < 60:
            if conn.hasResult():
                break
            time.sleep(1)
        
        if timeout == 60:
            if self.debug:
                print '[Follwit API] Request has timed out.'
            data = {}
            data['response'] = 'failure'
            data['message'] = 'Request time follwit has timed out'
            data['data'] = jdata
            conn.close()
            return data
        
        response = conn.getResult()
        raw = response.read()
        try:
            data = json.loads(raw)
        except:
            if self.debug:
                print 'Invalid Response from follwit'
                print raw
            args['raw'] = raw
            data = {}
            data['response'] = 'failure'
            data['message'] = 'Invalid Response from follwit'
            data['data'] = json.dumps(data)
            conn.close()
            return data
        if self.debug:
            print 'Responce: '+json.dumps(data)
        conn.close()
        if 'response' in data:                
            if not data['response'] == 'success':
                args['password'] = '[Password Removed]'
                data['data'] = json.dumps(data)
        return data
    
    # method that preforms http requests to the follwit server
    # @param[0] method     type of request (GET, POST)
    # @param[1] req        path of request after hostname /api/3/...
    # @param[2] username   username to use for requests
    # @param[3] password   password to use for requests
    # @param[4] args       dictionary of arguments to send to api
    # @param[5] host       hostname to send request to
    # @param[6] apikey     apikey to use
    # @param[7] debug decides if method waits for a return value
    def threadedJsonRequest(self,info):
        info = list(info)
        method=info[0]
        req=info[1]
        username=info[2]
        password=info[3]
        args=info[4]
        host=info[5]
        apikey=info[6]
        debug=info[7]
        
        try:
            conn = NBHTTPConnection(host)
        except socket.timeout:
            print '[Follwit API] socket timeout excecption'
            conn = None
        if conn == None:
            if debug:
                print '[Follwit API] Unable to connect to follwit'
            data = {}
            data['response'] = 'failure'
            data['message'] = 'Unable to connect to follwit'
            info[3] = '[Password Removed]'
            data['data'] = info
            return data
    
        try:
            req = req.replace('${apikey}',apikey)
            if method == 'POST':
                args['username'] = username
                args['password'] = password
                jdata = json.dumps(args)
                conn.request('POST', req, jdata)
            elif method == 'GET':
                conn.request('GET', req)
            else:
                return 'method error'
        except socket.error:
            if debug:
                print 'Unable to connect to follwit'
            data = {}
            data['response'] = 'failure'
            data['message'] = 'Unable to connect to follwit'
            info[3] = '[Password Removed]'
            data['data'] = info
            conn.close()
            return data
        jdata = str(jdata).replace(self.password,'[Password Removed]')
        if debug:
            print 'Request string ' + req
            print 'Request args ' + jdata
        
        conn.go()
        timeout = 0
        # one minute timeout on request before moving on.
        while timeout < 60:
            if conn.hasResult():
                break
            time.sleep(1)
            timeout = timeout + 1
        if timeout == 60:
            if self.debug:
                print '[Follwit API] Request has timed out.'
            data = {}
            data['response'] = 'failure'
            data['message'] = 'Request time follwit has timed out'
            data['data'] = jdata
            conn.close()
            return data
        
        response = conn.getResult()
        raw = response.read()
        try:
            data = json.loads(raw)
        except:
            if debug:
                print 'Invalid Response from follwit'
                print raw
            info.append(raw)
            data = {}
            data['response'] = 'failure'
            data['message'] = 'Invalid Response from follwit'
            data['data'] = json.dumps(data)
            conn.close()
            return data
        if debug:
            print 'Responce: '+json.dumps(data)
        conn.close()
        if 'response' in data:                
            if not data['response'] == 'success':
                data['data'] = json.dumps(data)
        return data

    
    # Method to check if return from jsonRequst was successful
    # @param data       return from self.jsonRequest
    # @param method     name of method calling this method (for logging)
    def successfulResponce(self, data, method='FollwitAPI'):
        if type(data) == type(list()):
            #this is a bulk item responce add information to the queue
            if self.debug:
                print '[Follwit API] Adding responce to responceQueue'
            self.responseQueue.put(data)
        else:
            if 'response' in data:                
                if data['response'] == 'success':
                    return True
                else:
                    print '[Follwit API] '+method+' request failed. Error: ' + data['message']
                    print data['data']
                    return False
            else:
                #unknown error has happened.
                print '[Follwit API] Unknown error in '+method
                return False
    
    # method to verify creditials for user account (self.username self.password)
    def authUser(self):
        data =  self.remoteAPI('POST', 'user.authenticate')
        try:
            if data['response'] == 'success':
                return True
            else:
                return False
        except:
            #exception occurred in jsonRequest
            return False
    
    # add a movie to a user's follwit collection
    # @param id_type        string value of movie_id, imdb_id, or tmdb_id
    # @param id             id of movie
    # @param showInStream   bool value to determine if call should show in the follwit stream
    # @param queue          bool to determine if request should be pushed to threadpool
    def addMovie(self, id_type, id, showInStream=True, queue=True):
        #Check for valid id_type
        if id_type in self.acceptedIDs:
            apiData = {id_type: id, 'insert_in_stream': showInStream}
            data =  self.remoteAPI('POST', 'movie.collection', apiData, queue)
            if queue:
                return True
        else:
            if self.debug:
                print 'Invalid id_type in addMovie request. Type Given: ' + id_type
            return False
        return self.successfulResponce(data, 'addMovie')
        
    # remove a movie to a user's follwit collection
    # @param id_type    string value of movie_id, imdb_id, or tmdb_id
    # @param id         id of movie
    # @param queue          bool to determine if request should be pushed to threadpool
    def removeMovie(self, id_type, id, queue=True):
        #Check for valid id_type
        if id_type in self.acceptedIDs:
            apiData = {id_type: id}
            data =  self.remoteAPI('POST', 'movie.uncollection', apiData, queue)
            if queue:
                return True
        else:
            if self.debug:
                print 'Invalid id_type in removeMovie request. Type Given: ' + id_type
            return False
        return self.successfulResponce(data, 'removeMovie')
        
    # set a movie to watched for a user
    # @param id_type        string value of movie_id, imdb_id, or tmdb_id
    # @param id             id of movie
    # @param showInStream   bool value to determine if call should show in the follwit stream
    # @param queue          bool to determine if request should be pushed to threadpool
    def watchedMovie(self, id_type, id, showInStream=True,queue=True):
        #Check for valid id_type
        if id_type in self.acceptedIDs:
            apiData = {id_type: id, 'insert_in_stream': showInStream}
            data =  self.remoteAPI('POST', 'movie.watched', apiData, queue)
            if queue:
                return True
        else:
            if self.debug:
                print 'Invalid id_type in watchedMovie request. Type Given: ' + id_type
            return False
        return self.successfulResponce(data, 'watchedMovie')
        
    
    # set a movie to watched for a user
    # @param id_type        string value of movie_id, imdb_id, or tmdb_id
    # @param id             id of movie
    # @param queue              bool to determine if request should be pushed to threadpool
    def notWatchedMovie(self, id_type, id, queue=True):
        #Check for valid id_type
        if id_type in self.acceptedIDs:
            apiData = {id_type: id}
            data =  self.remoteAPI('POST', 'movie.unwatched', apiData, queue)
            if queue:
                return True
        else:
            if self.debug:
                print 'Invalid id_type in notWatchedMovie request. Type Given: ' + id_type
            return False
        return self.successfulResponce(data, 'notWatchedMovie')
    
    # set a movie as watching for a user
    # @param id_type        string value of movie_id, imdb_id, or tmdb_id
    # @param id             id of movie
    # @param queue              bool to determine if request should be pushed to threadpool
    def watchingMovie(self, id_type, id, queue=True):
        #Check for valid id_type
        if id_type in self.acceptedIDs:
            apiData = {id_type: id}
            data =  self.remoteAPI('POST', 'movie.watching', apiData, queue)
            if queue:
                return True
        else:
            if self.debug:
                print 'Invalid id_type in Watching request. Type Given: ' + id_type
            return False
        return self.successfulResponce(data, 'watchingMovie')
    
    # remove a movie's watching flag for a user
    # @param id_type        string value of movie_id, imdb_id, or tmdb_id
    # @param id             id of movie
    # @param queue              bool to determine if request should be pushed to threadpool
    def notWatchingMovie(self, id_type, id, queue=True):
        #Check for valid id_type
        if id_type in self.acceptedIDs:
            apiData = {id_type: id}
            data =  self.remoteAPI('POST', 'movie.unwatching', apiData, queue)
            if queue:
                return True
        else:
            if self.debug:
                print 'Invalid id_type in notWatching request. Type Given: ' + id_type
            return False
        return self.successfulResponce(data, 'notWatchingMovie')
    
    # add an episode to a user's collection
    # @param tvdb_series_id     tvdb_series_id of item
    # @param season_number     season number of item
    # @param episode_number     episode_number of item
    # @param queue              bool to determine if request should be pushed to threadpool
    def addEpisode(self, tvdb_series_id, season_number, episode_number, queue=True):
        apiData = {'tvdb_series_id': tvdb_series_id, 'season_number': season_number, 'episode_number': episode_number}
        data =  self.remoteAPI('POST', 'episode.collection', apiData, queue)
        if queue:
            return True
        return self.successfulResponce(data, 'addEpisode')
    
    # remove an episode to a user's collection
    # @param tvdb_series_id     tvdb_series_id of item
    # @param season_number     season number of item
    # @param episode_number     episode_number of item
    # @param queue              bool to determine if request should be pushed to threadpool
    def removeEpisode(self, tvdb_series_id, season_number, episode_number, queue=True):
        apiData = {'tvdb_series_id': tvdb_series_id, 'season_number': season_number, 'episode_number': episode_number}
        data =  self.remoteAPI('POST', 'episode.uncollection', apiData, queue)
        if queue:
            return True
        return self.successfulResponce(data, 'removeEpisode')
    
    # set an episode as watched in a user's collection
    # @param tvdb_series_id     tvdb_series_id of item
    # @param season_number     season number of item
    # @param episode_number     episode_number of item
    # @param insert_in_stream     show event in stream
    # @param queue              bool to determine if request should be pushed to threadpool
    def watchedEpisode(self, tvdb_series_id, season_number, episode_number, insert_in_stream=True, queue=True):
        apiData = {'tvdb_series_id': tvdb_series_id, 'season_number': season_number, 'episode_number': episode_number, 'insert_in_stream': insert_in_stream}
        data =  self.remoteAPI('POST', 'episode.watched', apiData, queue)
        if queue:
            return True
        return self.successfulResponce(data, 'watchedEpisode')
    
    # set an episode as not watched in a user's collection
    # @param tvdb_series_id     tvdb_series_id of item
    # @param season_number     season number of item
    # @param episode_number     episode_number of item
    # @param queue              bool to determine if request should be pushed to threadpool
    def notWatchedEpisode(self, tvdb_series_id, season_number, episode_number, queue=True):
        apiData = {'tvdb_series_id': tvdb_series_id, 'season_number': season_number, 'episode_number': episode_number}
        data =  self.remoteAPI('POST', 'episode.unwatched', apiData, queue)
        if queue:
            return True
        return self.successfulResponce(data, 'notWatchedEpisode')
    
    # set an episode as watcheding in a user's stream
    # @param tvdb_series_id     tvdb_series_id of item
    # @param season_number     season number of item
    # @param episode_number     episode_number of item
    # @param queue              bool to determine if request should be pushed to threadpool
    def watchingEpisode(self, tvdb_series_id, season_number, episode_number, queue=True):
        apiData = {'tvdb_series_id': tvdb_series_id, 'season_number': season_number, 'episode_number': episode_number}
        data =  self.remoteAPI('POST', 'episode.watching', apiData, queue)
        if queue:
            return True
        return self.successfulResponce(data, 'watchingEpisode')
    
    # set an episode as not watcheding in a user's stream
    # @param tvdb_series_id     tvdb_series_id of item
    # @param season_number      season number of item
    # @param episode_number     episode_number of item
    # @param queue              bool to determine if request should be pushed to threadpool
    def notWatchingEpisode(self, tvdb_series_id, season_number, episode_number, queue=True):
        apiData = {'tvdb_series_id': tvdb_series_id, 'season_number': season_number, 'episode_number': episode_number}
        data =  self.remoteAPI('POST', 'episode.unwatching', apiData, queue)
        if queue:
            return True
        return self.successfulResponce(data, 'notWatchingEpisode')
    
